1. 本章节中所用到的表

# models.py

# 班级表
class Classes(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)


# 教师表
class Teacher(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)
    classes = models.ManyToManyField(to='Classes')


# 学生表
class Student(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)
    age = models.IntegerField()
    classes = models.ForeignKey(to='Classes')
    student_info = models.OneToOneField(to='StudentInfo')


# 学生详情表
class StudentInfo(models.Model):
    address = models.CharField(max_length=32)

2. 错误用法

  • 不要在通过.all()方法查询到的数据中进行跨表查询,因为这样会影响性能

student_list = Student.objects.all()

for student in student_list:
    print(student.name)  # Kevin -> 直接从student对象中获取
    print(student.age)  # 18 -> 直接从student对象中获取
    print(student.classes.name)  # 一班 -> 需要再执行SQL语句查询获取 -> 注意: 不要在通过.all()方法查询到的数据中进行跨表查询,因为 student.name 和 student.age 都是直接在student对象中获取数据的,而执行到 student.classes.name 这里的时候就要再执行一次 SQL 查询将当前student所关联的 classes.name 查询出来,如果 student_list 有500条数据,那么 student.classes.name 就要执行500条SQL语句

"""
    所执行的SQL语句次数
        select "app01_student"."id", "app01_student"."name", "app01_student"."age", "app01_student"."classes_id", "app01_student"."student_info_id" from "app01_student"; args=()
        select "app01_classes"."id", "app01_classes"."name" from "app01_classes" where "app01_classes"."id" = 1; args=(1,)
        select "app01_classes"."id", "app01_classes"."name" from "app01_classes" where "app01_classes"."id" = 1; args=(1,)
        select "app01_classes"."id", "app01_classes"."name" from "app01_classes" where "app01_classes"."id" = 2; args=(2,)
"""

3..select_related()

  • 说明
    • select_related主要针一对一和一对多(正向查询)关系进行优化
    • select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能
    • 性能相关:表之间进行join连表操作,一次性获取关联的数据

  • 不传参 -> 代表当前表 join 与当前表相关联的所有表

    • 用法一

student_list = Student.objects.all().select_related()

for student in student_list:
    print(student.name)  # Kevin -> 直接从student对象中获取
    print(student.age)  # 18 -> 直接从student对象中获取
    print(student.classes.name)  # 一班 -> 直接从student对象中获取
    print(student.student_info.address)  # 横沥 -> 直接从student对象中获取

"""
    所执行的SQL语句次数
        select "app01_student"."id", "app01_student"."name", "app01_student"."age", "app01_student"."classes_id", "app01_student"."student_info_id", "app01_classes"."id", "app01_classes"."name", "app01_studentinfo"."id", "app01_studentinfo"."address" from "app01_student" inner join "app01_classes" on ("app01_student"."classes_id" = "app01_classes"."id") inner join "app01_studentinfo" on ("app01_student"."student_info_id" = "app01_studentinfo"."id"); args=()
"""

    • 用法二

# 查询每个学生所在的班级

ret = Student.objects.all().select_related().values('name', 'classes__name')

ret = Student.objects.all().select_related().filter(age=18)

  • 传参 -> 代表当前表 join 指定与当前表相关联的表

    • 语法: .select_related('外键字段名', '外键字段名', ……)

    • 用法一

student_list = Student.objects.all().select_related('classes')

for student in student_list:
    print(student.name)  # Kevin -> 直接从student对象中获取
    print(student.age)  # 18 -> 直接从student对象中获取
    print(student.classes.name)  # 一班 -> 直接从student对象中获取

"""
    所执行的SQL语句次数
        SELECT "app01_student"."id", "app01_student"."name", "app01_student"."age", "app01_student"."classes_id", "app01_student"."student_info_id", "app01_classes"."id", "app01_classes"."name" FROM "app01_student" INNER JOIN "app01_classes" ON ("app01_student"."classes_id" = "app01_classes"."id"); args=()
"""

student_list = Student.objects.all().select_related('classes')

for student in student_list:
    print(student.name)  # Kevin -> 直接从student对象中获取
    print(student.age)  # 18 -> 直接从student对象中获取
    print(student.classes.name)  # 一班 -> 直接从student对象中获取
    print(student.student_info.address)  # 横沥 -> 需要再执行SQL语句查询获取 -> 注意: 如果 select_related 没有指定 student_info 外键字段那么也会再执行SQL语句查询获取,和上面的错误用法的注意事项一样

"""
    所执行的SQL语句次数
        SELECT "app01_student"."id", "app01_student"."name", "app01_student"."age", "app01_student"."classes_id", "app01_student"."student_info_id", "app01_classes"."id", "app01_classes"."name" FROM "app01_student" INNER JOIN "app01_classes" ON ("app01_student"."classes_id" = "app01_classes"."id"); args=()
        SELECT "app01_studentinfo"."id", "app01_studentinfo"."address" FROM "app01_studentinfo" WHERE "app01_studentinfo"."id" = 1; args=(1,)
        SELECT "app01_studentinfo"."id", "app01_studentinfo"."address" FROM "app01_studentinfo" WHERE "app01_studentinfo"."id" = 2; args=(2,)
        SELECT "app01_studentinfo"."id", "app01_studentinfo"."address" FROM "app01_studentinfo" WHERE "app01_studentinfo"."id" = 3; args=(3,)
"""

student_list = Student.objects.all().select_related('classes', 'student_info')

for student in student_list:
    print(student.name)  # Kevin -> 直接从student对象中获取
    print(student.age)  # 18 -> 直接从student对象中获取
    print(student.classes.name)  # 一班 -> 直接从student对象中获取
    print(student.student_info.address)  # 横沥 -> 直接从student对象中获取

"""
    所执行的SQL语句次数
        SELECT "app01_student"."id", "app01_student"."name", "app01_student"."age", "app01_student"."classes_id", "app01_student"."student_info_id", "app01_classes"."id", "app01_classes"."name", "app01_studentinfo"."id", "app01_studentinfo"."address" FROM "app01_student" INNER JOIN "app01_classes" ON ("app01_student"."classes_id" = "app01_classes"."id") INNER JOIN "app01_studentinfo" ON ("app01_student"."student_info_id" = "app01_studentinfo"."id"); args=()
"""

    • 用法二

# 查询每个学生所在的班级

ret = Student.objects.all().select_related('classes').values('name', 'classes__name')

ret = Student.objects.all().select_related('classes').filter(age=18)

4..prefetch_related()

  • 对于多对多字段(ManyToManyField)和一对多(反向查询),可以使用prefetch_related()来进行优化
  • prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系
  • 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作

  • 原理

1. select * form classes;

2. 通过python代码获取id: c_id=[1, 2 ,3]

3. select * from teacher where id in c_id

  • 错误用法

    • 注意: .prefetch_related() 一定要传参,否则和普通的.all()方法的性能一样

teacher_list = Teacher.objects.all().prefetch_related()

for teacher in teacher_list:
    for classes in teacher.classes.all():
        print(classes.name)  # 一班

"""
    所执行的SQL语句次数
        SELECT "app01_teacher"."id", "app01_teacher"."name" FROM "app01_teacher"; args=()
        SELECT "app01_classes"."id", "app01_classes"."name" FROM "app01_classes" INNER JOIN "app01_teacher_classes" ON ("app01_classes"."id" = "app01_teacher_classes"."classes_id") WHERE "app01_teacher_classes"."teacher_id" = 1; args=(1,)
        SELECT "app01_classes"."id", "app01_classes"."name" FROM "app01_classes" INNER JOIN "app01_teacher_classes" ON ("app01_classes"."id" = "app01_teacher_classes"."classes_id") WHERE "app01_teacher_classes"."teacher_id" = 2; args=(2,)
        SELECT "app01_classes"."id", "app01_classes"."name" FROM "app01_classes" INNER JOIN "app01_teacher_classes" ON ("app01_classes"."id" = "app01_teacher_classes"."classes_id") WHERE "app01_teacher_classes"."teacher_id" = 3; args=(3,)
"""

  • 多对多的正向查询

    • 语法: .prefetch_related('类的ManyToManyField属性名', ……)

teacher_list = Teacher.objects.all().prefetch_related('classes')

for teacher in teacher_list:
    for classes in teacher.classes.all():
        print(classes.name)  # 一班

"""
    所执行的SQL语句次数
        select "app01_teacher"."id", "app01_teacher"."name" from "app01_teacher"; args=()
        select ("app01_teacher_classes"."teacher_id") as "_prefetch_related_val_teacher_id", "app01_classes"."id", "app01_classes"."name" from "app01_classes" inner join "app01_teacher_classes" on ("app01_classes"."id" = "app01_teacher_classes"."classes_id") where "app01_teacher_classes"."teacher_id" in (1, 2, 3); args=(1, 2, 3)
"""

  • 多对多的反向查询

    • 语法一: .prefetch_related('主表的类名_set', ……) 
    • 语法二: .prefetch_related('related_name所设置的名字', ……)

classes_list = Classes.objects.all().prefetch_related('teacher_set')

for classes in classes_list:
    for teacher in classes.teacher_set.all():
        print(teacher.name)  # 李老师

"""
    所执行的SQL语句次数
        select "app01_classes"."id", "app01_classes"."name" from "app01_classes"; args=()
        select ("app01_teacher_classes"."classes_id") as "_prefetch_related_val_classes_id", "app01_teacher"."id", "app01_teacher"."name" from "app01_teacher" inner join "app01_teacher_classes" on ("app01_teacher"."id" = "app01_teacher_classes"."teacher_id") where "app01_teacher_classes"."classes_id" in (1, 2); args=(1, 2)
"""

  • 一对多的反向查询

    • 语法一: .prefetch_related('主表的类名_set', ……) 
    • 语法二: .prefetch_related('related_name所设置的名字', ……)

classes_list = Classes.objects.all().prefetch_related('student_set')

for classes in classes_list:
    for student in classes.student_set.all():
        print(student.name)  # Kevin

"""
    所执行的SQL语句次数
        select "app01_classes"."id", "app01_classes"."name" from "app01_classes"; args=()
        select "app01_student"."id", "app01_student"."name", "app01_student"."age", "app01_student"."classes_id", "app01_student"."student_info_id" from "app01_student" where "app01_student"."classes_id" in (1, 2); args=(1, 2)
"""

  • 其他用法

# 查询每个班级下的学生

ret = Classes.objects.prefetch_related().values('name', 'student__name')